/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

//import { CodeLensProvider, CodeLens, CancellationToken, TextDocument, Range, Uri, Position, Event, EventEmitter } from 'vscode';
//import * as Proto from '../protocol';

import { ITypeScriptServiceClient } from '../typescriptService';
import { tsTextSpanToVsRange } from '../utils/convert';
import { escapeRegExp } from '../utils/regexp';

export class CodeLens {

	range: any;

	command: any;

	constructor(range: any, command?: any) {
		this.range = range;
		this.command = command;
	}

	get isResolved(): boolean {
		return !!this.command;
	}
}
export class ReferencesCodeLens extends CodeLens {
	constructor(
		public document: any,
		public file: string,
		range: Range
	) {
		super(range);
	}
}

export class CachedNavTreeResponse {
	private response?: Promise<any>;
	private version: number = -1;
	private document: string = '';

	public execute(
		document: any,
		f: () => Promise<any>
	) {
		if (this.matches(document)) {
			return this.response;
		}

		return this.update(document, f());
	}

	private matches(document: any): boolean {
		return this.version === document.version && this.document === document.uri.toString();
	}

	private update(
		document: any,
		response: Promise<any>
	): Promise<any> {
		this.response = response;
		this.version = document.version;
		this.document = document.uri.toString();
		return response;
	}
}

export abstract class TypeScriptBaseCodeLensProvider {
	private enabled: boolean = true;
	private onDidChangeCodeLensesEmitter: any;

	public constructor(
		protected client: ITypeScriptServiceClient,
		private cachedResponse: CachedNavTreeResponse,
		public vscode: any
	) {
		this.onDidChangeCodeLensesEmitter = new this.vscode.EventEmitter();
	}

	public get onDidChangeCodeLenses() {
		return this.onDidChangeCodeLensesEmitter.event;
	}

	protected setEnabled(enabled: false): void {
		if (this.enabled !== enabled) {
			this.enabled = enabled;
			this.onDidChangeCodeLensesEmitter.fire();
		}
	}

	async provideCodeLenses(document: any, token: any): Promise<any[]> {
		if (!this.enabled) {
			return [];
		}

		const filepath = this.client.normalizePath(document.uri);
		if (!filepath) {
			return [];
		}

		try {
			const response = await this.cachedResponse.execute(document, () => this.client.execute('navtree', { file: filepath }, token));
			if (!response) {
				return [];
			}

			const tree = response.body;
			const referenceableSpans: Range[] = [];
			if (tree && tree.childItems) {
				tree.childItems.forEach((item: any) => this.walkNavTree(document, item, null, referenceableSpans));
			}
			return referenceableSpans.map(span => {
				var codelens = new this.vscode.CodeLens(span);
				codelens.document = document.uri;
				codelens.file = filepath;
				return codelens;
			});
		} catch {
			return [];
		}
	}

	protected abstract extractSymbol(
		document: any,
		item: any,
		parent: any | null
	): any | null;

	private walkNavTree(
		document: any,
		item: any,
		parent: any | null,
		results: any[]
	): void {
		if (!item) {
			return;
		}

		const range = this.extractSymbol(document, item, parent);
		if (range) {
			results.push(range);
		}

		(item.childItems || []).forEach((child: any) => this.walkNavTree(document, child, item, results));
	}

	/**
	 * TODO: TS currently requires the position for 'references 'to be inside of the identifer
	 * Massage the range to make sure this is the case
	 */
	protected getSymbolRange(document: any, item: any): Range | null {
		if (!item) {
			return null;
		}

		const span = item.spans && item.spans[0];
		if (!span) {
			return null;
		}

		const range = tsTextSpanToVsRange(span, this.vscode);
		const text = document.getText(range);

		const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, 'gm');
		const match = identifierMatch.exec(text);
		const prefixLength = match ? match.index + match[1].length : 0;
		const startOffset = document.offsetAt(new this.vscode.Position(range.start.line, range.start.character)) + prefixLength;
		return new this.vscode.Range(
			document.positionAt(startOffset),
			document.positionAt(startOffset + item.text.length));
	}
}
